home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / python-support / gnome-orca / orca / braille.py < prev    next >
Encoding:
Python Source  |  2009-04-13  |  49.3 KB  |  1,465 lines

  1. # Orca
  2. #
  3. # Copyright 2005-2008 Sun Microsystems Inc.
  4. #
  5. # This library is free software; you can redistribute it and/or
  6. # modify it under the terms of the GNU Library General Public
  7. # License as published by the Free Software Foundation; either
  8. # version 2 of the License, or (at your option) any later version.
  9. #
  10. # This library is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  13. # Library General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU Library General Public
  16. # License along with this library; if not, write to the
  17. # Free Software Foundation, Inc., Franklin Street, Fifth Floor,
  18. # Boston MA  02110-1301 USA.
  19.  
  20. """A very experimental approach to the refreshable Braille display.  This
  21. module treats each line of the display as a sequential set of regions, where
  22. each region can potentially backed by an Accessible object.  Depending upon
  23. the Accessible object, the cursor routing keys can be used to perform
  24. operations on the Accessible object, such as invoking default actions or
  25. moving the text caret.
  26. """
  27.  
  28. __id__        = "$Id: braille.py 4311 2008-10-30 12:50:03Z wwalker $"
  29. __version__   = "$Revision: 4311 $"
  30. __date__      = "$Date: 2008-10-30 08:50:03 -0400 (Thu, 30 Oct 2008) $"
  31. __copyright__ = "Copyright (c) 2005-2008 Sun Microsystems Inc."
  32. __license__   = "LGPL"
  33.  
  34. import logging
  35. log = logging.getLogger("braille")
  36.  
  37. import signal
  38.  
  39. try:
  40.     import louis
  41. except ImportError:
  42.     louis = None
  43.     _defaultContractionTable = None
  44. else:
  45.     _defaultContractionTable = louis.getDefaultTable()
  46.  
  47. # We'll use the official BrlAPI pythons (as of BrlTTY 3.8) if they
  48. # are available.  Otherwise, we'll fall back to our own bindings.
  49. #
  50. try:
  51.     import brlapi
  52.     import gobject
  53.  
  54.     brlAPI = None
  55.     useBrlAPIBindings = True
  56.     brlAPIRunning = False
  57.     brlAPISourceId = 0
  58. except:
  59.     import brl
  60.     useBrlAPIBindings = False
  61.     brlAPIRunning = False
  62.  
  63. try:
  64.     # This can fail due to gtk not being available.  We want to
  65.     # be able to recover from that if possible.  The main driver
  66.     # for this is to allow "orca --text-setup" to work even if
  67.     # the desktop is not running.
  68.     #
  69.     import brlmon
  70. except:
  71.     pass
  72. import debug
  73. import eventsynthesizer
  74. import orca_state
  75. import settings
  76.  
  77. from orca_i18n import _                          # for gettext support
  78.  
  79. # If True, this module has been initialized.
  80. #
  81. _initialized = False
  82.  
  83. # The braille monitor
  84. #
  85. monitor = None
  86.  
  87. # Each of these maps to BrlAPI's brldefs.h file.
  88. #
  89. CMD_NOOP              = 0x00
  90. CMD_LNUP              = 0x01
  91. CMD_LNDN              = 0x02
  92. CMD_WINUP             = 0x03
  93. CMD_WINDN             = 0x04
  94. CMD_PRDIFLN           = 0x05
  95. CMD_NXDIFLN           = 0x06
  96. CMD_ATTRUP            = 0x07
  97. CMD_ATTRDN            = 0x08
  98. CMD_TOP               = 0x09
  99. CMD_BOT               = 0x0a
  100. CMD_TOP_LEFT          = 0x0b
  101. CMD_BOT_LEFT          = 0x0c
  102. CMD_PRPGRPH           = 0x0d
  103. CMD_NXPGRPH           = 0x0e
  104. CMD_PRPROMPT          = 0x0f
  105. CMD_NXPROMPT          = 0x10
  106. CMD_PRSEARCH          = 0x11
  107. CMD_NXSEARCH          = 0x12
  108. CMD_CHRLT             = 0x13
  109. CMD_CHRRT             = 0x14
  110. CMD_HWINLT            = 0x15
  111. CMD_HWINRT            = 0x16
  112. CMD_FWINLT            = 0x17
  113. CMD_FWINRT            = 0x18
  114. CMD_FWINLTSKIP        = 0x19
  115. CMD_FWINRTSKIP        = 0x1a
  116. CMD_LNBEG             = 0x1b
  117. CMD_LNEND             = 0x1c
  118. CMD_HOME              = 0x1d
  119. CMD_BACK              = 0x1e
  120. CMD_FREEZE            = 0x1f
  121. CMD_DISPMD            = 0x20
  122. CMD_SIXDOTS           = 0x21
  123. CMD_SLIDEWIN          = 0x22
  124. CMD_SKPIDLNS          = 0x23
  125. CMD_SKPBLNKWINS       = 0x24
  126. CMD_CSRVIS            = 0x25
  127. CMD_CSRHIDE           = 0x26
  128. CMD_CSRTRK            = 0x27
  129. CMD_CSRSIZE           = 0x28
  130. CMD_CSRBLINK          = 0x29
  131. CMD_ATTRVIS           = 0x2a
  132. CMD_ATTRBLINK         = 0x2b
  133. CMD_CAPBLINK          = 0x2c
  134. CMD_TUNES             = 0x2d
  135. CMD_HELP              = 0x2e
  136. CMD_INFO              = 0x2f
  137. CMD_LEARN             = 0x30
  138. CMD_PREFMENU          = 0x31
  139. CMD_PREFSAVE          = 0x32
  140. CMD_PREFLOAD          = 0x33
  141. CMD_MENU_FIRST_ITEM   = 0x34
  142. CMD_MENU_LAST_ITEM    = 0x35
  143. CMD_MENU_PREV_ITEM    = 0x36
  144. CMD_MENU_NEXT_ITEM    = 0x37
  145. CMD_MENU_PREV_SETTING = 0x38
  146. CMD_MENU_NEXT_SETTING = 0x39
  147. CMD_SAY_LINE          = 0x3a
  148. CMD_SAY_ABOVE         = 0x3b
  149. CMD_SAY_BELOW         = 0x3c
  150. CMD_MUTE              = 0x3d
  151. CMD_SPKHOME           = 0x3e
  152. CMD_SWITCHVT_PREV     = 0x3f
  153. CMD_SWITCHVT_NEXT     = 0x40
  154. CMD_CSRJMP_VERT       = 0x41
  155. CMD_PASTE             = 0x42
  156. CMD_RESTARTBRL        = 0x43
  157. CMD_RESTARTSPEECH     = 0x44
  158. CMD_MAX               = 0x44
  159.  
  160. BRL_FLG_REPEAT_INITIAL = 0x800000
  161. BRL_FLG_REPEAT_DELAY   = 0x400000
  162.  
  163. # Common names for most used BrlTTY commands, to be shown in the GUI:
  164. # ATM, the ones used in default.py are:
  165. #
  166. command_name = {}
  167.  
  168. # Translators: this is a command for a button on a refreshable braille
  169. # display (an external hardware device used by people who are blind).
  170. # When pressing the button, the display scrolls to the left.
  171. #
  172. command_name[CMD_FWINLT]   = _("Line Left")
  173.  
  174. # Translators: this is a command for a button on a refreshable braille
  175. # display (an external hardware device used by people who are blind).
  176. # When pressing the button, the display scrolls to the right.
  177. #
  178. command_name[CMD_FWINRT]   = _("Line Right")
  179.  
  180. # Translators: this is a command for a button on a refreshable braille
  181. # display (an external hardware device used by people who are blind).
  182. # When pressing the button, the display scrolls up.
  183. #
  184. command_name[CMD_LNUP]     = _("Line Up")
  185.  
  186. # Translators: this is a command for a button on a refreshable braille
  187. # display (an external hardware device used by people who are blind).
  188. # When pressing the button, the display scrolls down.
  189. #
  190. command_name[CMD_LNDN]     = _("Line Down")
  191.  
  192. # Translators: this is a command for a button on a refreshable braille
  193. # display (an external hardware device used by people who are blind).
  194. # When pressing the button, it instructs the braille display to freeze.
  195. #
  196. command_name[CMD_FREEZE]     = _("Freeze")
  197.  
  198. # Translators: this is a command for a button on a refreshable braille
  199. # display (an external hardware device used by people who are blind).
  200. # When pressing the button, the display scrolls to the top left of the
  201. # window.
  202. #
  203. command_name[CMD_TOP_LEFT] = _("Top Left")
  204.  
  205. # Translators: this is a command for a button on a refreshable braille
  206. # display (an external hardware device used by people who are blind).
  207. # When pressing the button, the display scrolls to the bottom right of
  208. # the window.
  209. #
  210. command_name[CMD_BOT_LEFT] = _("Bottom Right")
  211.  
  212. # Translators: this is a command for a button on a refreshable braille
  213. # display (an external hardware device used by people who are blind).
  214. # When pressing the button, the display scrolls to position containing
  215. # the cursor.
  216. #
  217. command_name[CMD_HOME]     = _("Cursor Position")
  218.  
  219. # The size of the physical display (width, height).  The coordinate system of
  220. # the display is set such that the upper left is (0,0), x values increase from
  221. # left to right, and y values increase from top to bottom.
  222. #
  223. # For the purposes of testing w/o a braille display, we'll set the display
  224. # size to width=32 and height=1.
  225. #
  226. # [[[TODO: WDW - Only a height of 1 is support at this time.]]]
  227. #
  228. _displaySize = [32, 1]
  229.  
  230. # The list of lines on the display.  This represents the entire amount of data
  231. # to be drawn on the display.  It will be clipped by the viewport if too large.
  232. #
  233. _lines = []
  234.  
  235. # The region with focus.  This will be displayed at the home position.
  236. #
  237. _regionWithFocus = None
  238.  
  239. # The viewport is a rectangular region of size _displaySize whose upper left
  240. # corner is defined by the point (x, line number).  As such, the viewport is
  241. # identified solely by its upper left point.
  242. #
  243. viewport = [0, 0]
  244.  
  245. # The callback to call on a BrlTTY input event.  This is passed to
  246. # the init method.
  247. #
  248. _callback = None
  249.  
  250. # If True, the given portion of the currently displayed line is showing
  251. # on the display.
  252. #
  253. endIsShowing = False
  254. beginningIsShowing = False
  255.  
  256. # 1-based offset saying which braille cell has the cursor.  A value
  257. # of 0 means no cell has the cursor.
  258. #
  259. cursorCell = 0
  260.  
  261. def _printBrailleEvent(level, command):
  262.     """Prints out a Braille event.  The given level may be overridden
  263.     if the eventDebugLevel (see debug.setEventDebugLevel) is greater in
  264.     debug.py.
  265.  
  266.     Arguments:
  267.     - command: the BrlAPI command for the key that was pressed.
  268.     """
  269.  
  270.     debug.printInputEvent(
  271.         level,
  272.         "BRAILLE EVENT: %x" % command)
  273.  
  274. class Region:
  275.     """A Braille region to be displayed on the display.  The width of
  276.     each region is determined by its string.
  277.     """
  278.  
  279.     def __init__(self, string, cursorOffset=0, expandOnCursor=False):
  280.         """Creates a new Region containing the given string.
  281.  
  282.         Arguments:
  283.         - string: the string to be displayed
  284.         - cursorOffset: a 0-based index saying where to draw the cursor
  285.                         for this Region if it gets focus.
  286.         """
  287.  
  288.         if not string:
  289.             string = ""
  290.  
  291.         # If louis is None, then we don't go into contracted mode.
  292.         self.contracted = settings.enableContractedBraille and \
  293.                           louis is not None
  294.         
  295.         self.expandOnCursor = expandOnCursor
  296.         
  297.         # The uncontracted string for the line.
  298.         #
  299.         self.rawLine = string.decode("UTF-8").strip("\n")
  300.  
  301.         if self.contracted:
  302.             self.contractionTable = settings.brailleContractionTable or \
  303.                                     _defaultContractionTable
  304.  
  305.             self.string, self.inPos, self.outPos, self.cursorOffset = \
  306.                          self.contractLine(self.rawLine,
  307.                                            cursorOffset, expandOnCursor)
  308.         else:
  309.             self.string = self.rawLine
  310.             self.cursorOffset = cursorOffset
  311.             
  312.     def processRoutingKey(self, offset):
  313.         """Processes a cursor routing key press on this Component.  The offset
  314.         is 0-based, where 0 represents the leftmost character of string
  315.         associated with this region.  Note that the zeroeth character may have
  316.         been scrolled off the display."""
  317.         pass
  318.  
  319.     def getAttributeMask(self, getLinkMask=True):
  320.         """Creates a string which can be used as the attrOr field of brltty's
  321.         write structure for the purpose of indicating text attributes, links,
  322.         and selection.
  323.  
  324.         Arguments:
  325.         - getLinkMask: Whether or not we should take the time to get
  326.           the attributeMask for links. Reasons we might not want to
  327.           include knowning that we will fail and/or it taking an
  328.           unreasonable amount of time (AKA Gecko).
  329.         """
  330.  
  331.         # Double check for ellipses.
  332.         #
  333.         maskSize = len(self.string) + (2 * self.string.count(u'\u2026'))
  334.  
  335.         # Create an empty mask.
  336.         #
  337.         mask = ['\x00'] * maskSize
  338.         return "".join(mask)
  339.     
  340.     def repositionCursor(self):
  341.         """Reposition the cursor offset for contracted mode.
  342.         """
  343.         if self.contracted:
  344.             self.string, self.inPos, self.outPos, self.cursorOffset = \
  345.                        self.contractLine(self.rawLine,
  346.                                          self.cursorOffset,
  347.                                          self.expandOnCursor)
  348.  
  349.     def contractLine(self, line, cursorOffset=0, expandOnCursor=False):
  350.         """Contract the given line. Returns the contracted line, and the
  351.         cursor position in the contracted line.
  352.  
  353.         Arguments:
  354.         - line: Line to contract.
  355.         - cursorOffset: Offset of cursor,defaults to 0.
  356.         - expandOnCursor: Expand word under cursor, False by default.
  357.         """
  358.  
  359.         try:
  360.             cursorOnSpace = line[cursorOffset] == ' '
  361.         except IndexError:
  362.             cursorOnSpace = False
  363.             
  364.         if not expandOnCursor or cursorOnSpace:
  365.             contracted, inPos, outPos, cursorPos = \
  366.                              louis.translate([self.contractionTable],
  367.                                              line,
  368.                                              cursorPos=cursorOffset)
  369.         else:
  370.             contracted, inPos, outPos, cursorPos = \
  371.                              louis.translate([self.contractionTable],
  372.                                              line,
  373.                                              cursorPos=cursorOffset,
  374.                                              mode=louis.MODE.compbrlAtCursor)
  375.  
  376.         return contracted, inPos, outPos, cursorPos
  377.     
  378.     def displayToBufferOffset(self, display_offset):
  379.         try:
  380.             offset = self.inPos[display_offset]
  381.         except IndexError:
  382.             # Off the chart, we just place the cursor at the end of the line.
  383.             offset = len(self.rawLine)
  384.         except AttributeError:
  385.             # Not in contracted mode.
  386.             offset = display_offset
  387.  
  388.         return offset
  389.  
  390.     def setContractedBraille(self, contracted):
  391.         if self.contracted == contracted:
  392.             return
  393.         self.contracted = contracted
  394.         if contracted:
  395.             self.contractionTable = settings.brailleContractionTable or \
  396.                                     _defaultContractionTable
  397.             self.contractRegion()
  398.         else:
  399.             self.expandRegion()
  400.  
  401.     def contractRegion(self):
  402.         self.string, self.inPos, self.outPos, self.cursorOffset = \
  403.                      self.contractLine(self.rawLine,
  404.                                        self.cursorOffset,
  405.                                        self.expandOnCursor)
  406.         
  407.     def expandRegion(self):
  408.         if not self.contracted:
  409.             return
  410.         self.string = self.rawLine
  411.         try:
  412.             self.cursorOffset = self.inPos[self.cursorOffset]
  413.         except IndexError:
  414.             self.cursorOffset = len(self.string)
  415.         
  416. class Component(Region):
  417.     """A subclass of Region backed by an accessible.  This Region will react
  418.     to any cursor routing key events and perform the default action on the
  419.     accessible, if a default action exists.
  420.     """
  421.  
  422.     def __init__(self, accessible, string, cursorOffset=0,
  423.                  indicator='', expandOnCursor=False):
  424.         """Creates a new Component.
  425.  
  426.         Arguments:
  427.         - accessible: the accessible
  428.         - string: the string to use to represent the component
  429.         - cursorOffset: a 0-based index saying where to draw the cursor
  430.                         for this Region if it gets focus.
  431.         """
  432.  
  433.         Region.__init__(self, string, cursorOffset, expandOnCursor)
  434.         if indicator:
  435.             if self.string:
  436.                 self.string = indicator + ' ' + self.string
  437.             else:
  438.                 self.string = indicator
  439.  
  440.         self.accessible = accessible
  441.  
  442.     def processRoutingKey(self, offset):
  443.         """Processes a cursor routing key press on this Component.  The offset
  444.         is 0-based, where 0 represents the leftmost character of string
  445.         associated with this region.  Note that the zeroeth character may have
  446.         been scrolled off the display."""
  447.  
  448.         try:
  449.             action = self.accessible.queryAction()
  450.         except:
  451.             # Do a mouse button 1 click if we have to.  For example, page tabs
  452.             # don't have any actions but we want to be able to select them with
  453.             # the cursor routing key.
  454.             #
  455.             debug.println(debug.LEVEL_FINEST,
  456.                           "braille.Component.processRoutingKey: no action")
  457.             try:
  458.                 eventsynthesizer.clickObject(self.accessible, 1)
  459.             except:
  460.                 debug.printException(debug.LEVEL_SEVERE)
  461.         else:
  462.             action.doAction(0)
  463.  
  464. class Link(Component):
  465.     """A subclass of Component backed by an accessible.  This Region will be
  466.     marked as a link by dots 7 or 8, depending on the user's preferences.
  467.     """
  468.  
  469.     def __init__(self, accessible, string, cursorOffset=0):
  470.         """Initialize a Link region. similar to Component, but here we always
  471.         have the region expand on cursor."""
  472.         Component.__init__(self, accessible, string, cursorOffset, '', True)
  473.  
  474.     def getAttributeMask(self, getLinkMask=True):
  475.         """Creates a string which can be used as the attrOr field of brltty's
  476.         write structure for the purpose of indicating text attributes and
  477.         selection.
  478.         Arguments:
  479.  
  480.         - getLinkMask: Whether or not we should take the time to get
  481.           the attributeMask for links. Reasons we might not want to
  482.           include knowning that we will fail and/or it taking an
  483.           unreasonable amount of time (AKA Gecko).
  484.         """
  485.  
  486.         # Create an link indicator mask.
  487.         #
  488.         return chr(settings.brailleLinkIndicator) * len(self.string)
  489.  
  490. class Text(Region):
  491.     """A subclass of Region backed by a Text object.  This Region will
  492.     react to any cursor routing key events by positioning the caret in
  493.     the associated text object. The line displayed will be the
  494.     contents of the text object preceded by an optional label.
  495.     [[[TODO: WDW - need to add in text selection capabilities.  Logged
  496.     as bugzilla bug 319754.]]]"""
  497.  
  498.     def __init__(self, accessible, label="", eol="", 
  499.                  startOffset=None, endOffset=None):
  500.         """Creates a new Text region.
  501.  
  502.         Arguments:
  503.         - accessible: the accessible that implements AccessibleText
  504.         - label: an optional label to display
  505.         """
  506.  
  507.         self.accessible = accessible
  508.         if orca_state.activeScript:
  509.             [string, self.caretOffset, self.lineOffset] = \
  510.                  orca_state.activeScript.getTextLineAtCaret(self.accessible,
  511.                                                             startOffset)
  512.         else:
  513.             string = ""
  514.  
  515.         string = string.decode("UTF-8")
  516.         if label:
  517.             label = label.decode("UTF-8")
  518.         if eol:
  519.             eol = eol.decode("UTF-8")
  520.  
  521.         try:
  522.             endOffset = endOffset - self.lineOffset
  523.         except TypeError:
  524.             pass
  525.  
  526.         try:
  527.             self.startOffset = startOffset - self.lineOffset
  528.         except TypeError:
  529.             self.startOffset = 0
  530.  
  531.         string = string[self.startOffset:endOffset]
  532.  
  533.         self.caretOffset -= self.startOffset
  534.  
  535.         cursorOffset = min(self.caretOffset - self.lineOffset, len(string))
  536.  
  537.         self._maxCaretOffset = self.lineOffset + len(string)
  538.  
  539.         self.eol = eol
  540.  
  541.         if label:
  542.             self.label = label + ' '
  543.         else:
  544.             self.label = ''
  545.  
  546.         string = self.label + string
  547.  
  548.         cursorOffset += len(self.label)
  549.  
  550.         Region.__init__(self, string, cursorOffset, True)
  551.  
  552.         if not self.contracted and not settings.disableBrailleEOL:
  553.             self.string += self.eol
  554.  
  555.     def repositionCursor(self):
  556.         """Attempts to reposition the cursor in response to a new
  557.         caret position.  If it is possible (i.e., the caret is on
  558.         the same line as it was), reposition the cursor and return
  559.         True.  Otherwise, return False.
  560.         """
  561.  
  562.         [string, caretOffset, lineOffset] = \
  563.                  orca_state.activeScript.getTextLineAtCaret(self.accessible,
  564.                                                             self.startOffset)
  565.         string = string.decode("UTF-8")
  566.  
  567.         cursorOffset = min(caretOffset - lineOffset, len(string))
  568.         
  569.         if lineOffset != self.lineOffset:
  570.             return False
  571.  
  572.         self.caretOffset = caretOffset
  573.         self.lineOffset = lineOffset
  574.  
  575.         cursorOffset += len(self.label)
  576.  
  577.         if self.contracted:
  578.             self.string, self.inPos, self.outPos, cursorOffset = \
  579.                        self.contractLine(self.rawLine, cursorOffset, True)
  580.  
  581.         self.cursorOffset = cursorOffset
  582.  
  583.         return True
  584.  
  585.     def processRoutingKey(self, offset):
  586.         """Processes a cursor routing key press on this Component.  The offset
  587.         is 0-based, where 0 represents the leftmost character of text
  588.         associated with this region.  Note that the zeroeth character may have
  589.         been scrolled off the display."""
  590.         
  591.         offset = self.displayToBufferOffset(offset)
  592.  
  593.         if offset < 0:
  594.             return
  595.  
  596.         newCaretOffset = min(self.lineOffset + offset, self._maxCaretOffset)
  597.         orca_state.activeScript.setCaretOffset(
  598.             self.accessible, newCaretOffset)
  599.  
  600.     def getAttributeMask(self, getLinkMask=True):
  601.         """Creates a string which can be used as the attrOr field of brltty's
  602.         write structure for the purpose of indicating text attributes, links,
  603.         and selection.
  604.  
  605.         Arguments:
  606.         - getLinkMask: Whether or not we should take the time to get
  607.           the attributeMask for links. Reasons we might not want to
  608.           include knowning that we will fail and/or it taking an
  609.           unreasonable amount of time (AKA Gecko).
  610.         """
  611.  
  612.         try:
  613.             text = self.accessible.queryText()
  614.         except NotImplementedError:
  615.             return ''
  616.  
  617.         # Start with an empty mask.
  618.         #
  619.         stringLength = len(self.rawLine) - len(self.label)
  620.         lineEndOffset = self.lineOffset + stringLength
  621.         regionMask = [settings.TEXT_ATTR_BRAILLE_NONE]*stringLength
  622.  
  623.         attrIndicator = settings.textAttributesBrailleIndicator
  624.         selIndicator = settings.brailleSelectorIndicator
  625.         linkIndicator = settings.brailleLinkIndicator
  626.         script = orca_state.activeScript
  627.  
  628.         if getLinkMask and linkIndicator != settings.BRAILLE_LINK_NONE:
  629.             try:
  630.                 hyperText = self.accessible.queryHypertext()
  631.                 nLinks = hyperText.getNLinks()
  632.             except:
  633.                 nLinks = 0
  634.  
  635.             n = 0
  636.             while n < nLinks:
  637.                 link = hyperText.getLink(n)
  638.                 if self.lineOffset <= link.startIndex:
  639.                     for i in xrange(link.startIndex, link.endIndex):
  640.                         try:
  641.                             regionMask[i] |= linkIndicator
  642.                         except:
  643.                             pass
  644.                 n += 1
  645.  
  646.         if attrIndicator:
  647.             enabledAttributes = script.attribsToDictionary(
  648.                 settings.enabledBrailledTextAttributes)
  649.  
  650.             offset = self.lineOffset
  651.             while offset < lineEndOffset:
  652.                 attributes, startOffset, endOffset = \
  653.                             script.getTextAttributes(self.accessible,
  654.                                                      offset, True)
  655.                 if endOffset <= offset:
  656.                     break
  657.                 mask = settings.TEXT_ATTR_BRAILLE_NONE
  658.                 offset = endOffset
  659.                 for attrib in attributes:
  660.                     if enabledAttributes.get(attrib, '') != '':
  661.                         if enabledAttributes[attrib] != attributes[attrib]:
  662.                             mask = attrIndicator
  663.                             break
  664.                 if mask != settings.TEXT_ATTR_BRAILLE_NONE:
  665.                     maskStart = max(startOffset - self.lineOffset, 0)
  666.                     maskEnd = min(endOffset - self.lineOffset, stringLength)
  667.                     for i in xrange(maskStart, maskEnd):
  668.                         regionMask[i] |= attrIndicator
  669.  
  670.         if selIndicator:
  671.             selections = script.getTextSelections(self.accessible)
  672.             for startOffset, endOffset in selections:
  673.                 maskStart = max(startOffset - self.lineOffset, 0)
  674.                 maskEnd = min(endOffset - self.lineOffset, stringLength)
  675.                 for i in xrange(maskStart, maskEnd):
  676.                     regionMask[i] |= selIndicator
  677.  
  678.         if self.contracted:
  679.             contractedMask = [0] * len(self.rawLine)
  680.             outPos = self.outPos[len(self.label):]
  681.             if self.label:
  682.                 # Transform the offsets.
  683.                 outPos = \
  684.                        [offset - len(self.label) - 1 for offset in outPos]
  685.             for i, m in enumerate(regionMask):
  686.                 try:
  687.                     contractedMask[outPos[i]] |= m
  688.                 except IndexError:
  689.                     continue
  690.             regionMask = contractedMask[:len(self.string)]
  691.  
  692.         # Add empty mask characters for the EOL character as well as for
  693.         # any label that might be present.
  694.         #
  695.         regionMask += [0]*len(self.eol)
  696.  
  697.         if self.label:
  698.             regionMask = [0]*len(self.label) + regionMask
  699.  
  700.         return ''.join(map(chr, regionMask))
  701.  
  702.     def contractLine(self, line, cursorOffset=0, expandOnCursor=True):
  703.         contracted, inPos, outPos, cursorPos = Region.contractLine(
  704.             self, line, cursorOffset, expandOnCursor)
  705.         
  706.         return contracted + self.eol, inPos, outPos, cursorPos
  707.  
  708.     def displayToBufferOffset(self, display_offset):
  709.         offset = Region.displayToBufferOffset(self, display_offset)
  710.         offset += self.startOffset
  711.         offset -= len(self.label)
  712.         return offset
  713.  
  714.     def setContractedBraille(self, contracted):
  715.         Region.setContractedBraille(self, contracted)
  716.         if not contracted:
  717.             self.string += self.eol
  718.  
  719. class ReviewComponent(Component):
  720.     """A subclass of Component that is to be used for flat review mode."""
  721.  
  722.     def __init__(self, accessible, string, cursorOffset, zone):
  723.         """Creates a new Component.
  724.  
  725.         Arguments:
  726.         - accessible: the accessible
  727.         - string: the string to use to represent the component
  728.         - cursorOffset: a 0-based index saying where to draw the cursor
  729.                         for this Region if it gets focus.
  730.         - zone: the flat review Zone associated with this component
  731.         """
  732.         Component.__init__(self, accessible, string,
  733.                            cursorOffset, expandOnCursor=True)
  734.         self.zone = zone
  735.  
  736. class ReviewText(Region):
  737.     """A subclass of Region backed by a Text object.  This Region will
  738.     does not react to the caret changes, but will react if one updates
  739.     the cursorPosition.  This class is meant to be used by flat review
  740.     mode to show the current character position.
  741.     """
  742.  
  743.     def __init__(self, accessible, string, lineOffset, zone):
  744.         """Creates a new Text region.
  745.  
  746.         Arguments:
  747.         - accessible: the accessible that implements AccessibleText
  748.         - string: the string to use to represent the component
  749.         - lineOffset: the character offset into where the text line starts
  750.         - zone: the flat review Zone associated with this component
  751.         """
  752.         Region.__init__(self, string, expandOnCursor=True)
  753.         self.accessible = accessible
  754.         self.lineOffset = lineOffset
  755.         self.zone = zone
  756.  
  757.     def processRoutingKey(self, offset):
  758.         """Processes a cursor routing key press on this Component.  The offset
  759.         is 0-based, where 0 represents the leftmost character of text
  760.         associated with this region.  Note that the zeroeth character may have
  761.         been scrolled off the display."""
  762.  
  763.         offset = self.displayToBufferOffset(offset)
  764.         newCaretOffset = self.lineOffset + offset
  765.         orca_state.activeScript.setCaretOffset(self.accessible, newCaretOffset)
  766.  
  767. class Line:
  768.     """A horizontal line on the display.  Each Line is composed of a sequential
  769.     set of Regions.
  770.     """
  771.  
  772.     def __init__(self, region=None):
  773.         self.regions = []
  774.         self.string = ""
  775.         if region:
  776.             self.addRegion(region)
  777.  
  778.     def addRegion(self, region):
  779.         self.regions.append(region)
  780.  
  781.     def addRegions(self, regions):
  782.         self.regions.extend(regions)
  783.  
  784.     def getLineInfo(self, getLinkMask=True):
  785.         """Computes the complete string for this line as well as a
  786.         0-based index where the focused region starts on this line.
  787.         If the region with focus is not on this line, then the index
  788.         will be -1.
  789.  
  790.         Arguments:
  791.         - getLinkMask: Whether or not we should take the time to get
  792.           the attributeMask for links. Reasons we might not want to
  793.           include knowning that we will fail and/or it taking an
  794.           unreasonable amount of time (AKA Gecko).
  795.  
  796.         Returns [string, offsetIndex, attributeMask]
  797.         """
  798.  
  799.         string = ""
  800.         focusOffset = -1
  801.         attributeMask = ""
  802.         for region in self.regions:
  803.             if region == _regionWithFocus:
  804.                 focusOffset = len(string)
  805.             if region.string:
  806.                 # [[[TODO: WDW - HACK: Replace ellipses with "..."
  807.                 # The ultimate solution is to get i18n support into
  808.                 # BrlTTY.]]]
  809.                 #
  810.                 string += region.string.replace(u'\u2026', "...")
  811.             mask = region.getAttributeMask(getLinkMask)
  812.             attributeMask += mask
  813.  
  814.         return [string, focusOffset, attributeMask]
  815.  
  816.     def getRegionAtOffset(self, offset):
  817.         """Finds the Region at the given 0-based offset in this line.
  818.  
  819.         Returns the [region, offsetinregion] where the region is
  820.         the region at the given offset, and offsetinregion is the
  821.         0-based offset from the beginning of the region, representing
  822.         where in the region the given offset is."""
  823.  
  824.         # Translate the cursor offset for this line into a cursor offset
  825.         # for a region, and then pass the event off to the region for
  826.         # handling.
  827.         #
  828.         foundRegion = None
  829.         string = ""
  830.         pos = 0
  831.         for region in self.regions:
  832.             foundRegion = region
  833.             string = string + region.string
  834.             if len(string) > offset:
  835.                 break
  836.             else:
  837.                 pos = len(string)
  838.  
  839.         if offset >= len(string):
  840.             return [None, -1]
  841.         else:
  842.             return [foundRegion, offset - pos]
  843.  
  844.     def processRoutingKey(self, offset):
  845.         """Processes a cursor routing key press on this Component.  The offset
  846.         is 0-based, where 0 represents the leftmost character of string
  847.         associated with this line.  Note that the zeroeth character may have
  848.         been scrolled off the display."""
  849.  
  850.         [region, regionOffset] = self.getRegionAtOffset(offset)
  851.         if region:
  852.             region.processRoutingKey(regionOffset)
  853.  
  854.     def setContractedBraille(self, contracted):
  855.         for region in self.regions:
  856.             region.setContractedBraille(contracted)
  857.  
  858. def getRegionAtCell(cell):
  859.     """Given a 1-based cell offset, return the braille region
  860.     associated with that cell in the form of [region, offsetinregion]
  861.     where 'region' is the region associated with the cell and
  862.     'offsetinregion' is the 0-based offset of where the cell is
  863.     in the region, where 0 represents the beginning of the region, """
  864.  
  865.     if len(_lines) > 0:
  866.         offset = (cell - 1) + viewport[0]
  867.         lineNum = viewport[1]
  868.         return _lines[lineNum].getRegionAtOffset(offset)
  869.     else:
  870.         return [None, -1]
  871.  
  872. def clear():
  873.     """Clears the logical structure, but keeps the Braille display as is
  874.     (until a refresh operation).
  875.     """
  876.  
  877.     global _lines
  878.     global _regionWithFocus
  879.     global viewport
  880.  
  881.     _lines = []
  882.     _regionWithFocus = None
  883.     viewport = [0, 0]
  884.  
  885. def setLines(lines):
  886.     global _lines
  887.     _lines = lines
  888.  
  889. def addLine(line):
  890.     """Adds a line to the logical display for painting.  The line is added to
  891.     the end of the current list of known lines.  It is necessary for the
  892.     viewport to be over the lines and for refresh to be called for the new
  893.     line to be painted.
  894.  
  895.     Arguments:
  896.     - line: an instance of Line to add.
  897.     """
  898.  
  899.     _lines.append(line)
  900.     line._index = len(_lines)
  901.  
  902. def getShowingLine():
  903.     """Returns the Line that is currently being painted on the display.
  904.     """
  905.     return _lines[viewport[1]]
  906.  
  907. def setFocus(region, panToFocus=True, getLinkMask=True):
  908.     """Specififes the region with focus.  This region will be positioned
  909.     at the home position if panToFocus is True.
  910.  
  911.     Arguments:
  912.     - region: the given region, which much be in a line that has been
  913.       added to the logical display
  914.     - panToFocus: whether or not to position the region at the home
  915.       position
  916.     - getLinkMask: Whether or not we should take the time to get the
  917.       attributeMask for links. Reasons we might not want to include
  918.       knowning that we will fail and/or it taking an unreasonable
  919.       amount of time (AKA Gecko).
  920.     """
  921.  
  922.     global _regionWithFocus
  923.  
  924.     _regionWithFocus = region
  925.  
  926.     if not panToFocus or (not _regionWithFocus):
  927.         return
  928.  
  929.     # Adjust the viewport according to the new region with focus.
  930.     # The goal is to have the first cell of the region be in the
  931.     # home position, but we will give priority to make sure the
  932.     # cursor for the region is on the display.  For example, when
  933.     # faced with a long text area, we'll show the position with
  934.     # the caret vs. showing the beginning of the region.
  935.  
  936.     lineNum = 0
  937.     done = False
  938.     for line in _lines:
  939.         for reg in line.regions:
  940.             if reg == _regionWithFocus:
  941.                 viewport[1] = lineNum
  942.                 done = True
  943.                 break
  944.         if done:
  945.             break
  946.         else:
  947.             lineNum += 1
  948.  
  949.     line = _lines[viewport[1]]
  950.     [string, offset, attributeMask] = line.getLineInfo(getLinkMask)
  951.  
  952.     # If the cursor is too far right, we scroll the viewport
  953.     # so the cursor will be on the last cell of the display.
  954.     #
  955.     if _regionWithFocus.cursorOffset >= _displaySize[0]:
  956.         offset += _regionWithFocus.cursorOffset - _displaySize[0] + 1
  957.  
  958.     viewport[0] = max(0, offset)
  959.  
  960. def refresh(panToCursor=True, targetCursorCell=0, getLinkMask=True):
  961.     """Repaints the Braille on the physical display.  This clips the entire
  962.     logical structure by the viewport and also sets the cursor to the
  963.     appropriate location.  [[[TODO: WDW - I'm not sure how BrlTTY handles
  964.     drawing to displays with more than one line, so I'm only going to handle
  965.     drawing one line right now.]]]
  966.  
  967.     Arguments:
  968.  
  969.     - panToCursor: if True, will adjust the viewport so the cursor is
  970.       showing.
  971.     - targetCursorCell: Only effective if panToCursor is True.
  972.       0 means automatically place the cursor somewhere on the display so
  973.       as to minimize movement but show as much of the line as possible.
  974.       A positive value is a 1-based target cell from the left side of
  975.       the display and a negative value is a 1-based target cell from the
  976.       right side of the display.
  977.     - getLinkMask: Whether or not we should take the time to get the
  978.       attributeMask for links. Reasons we might not want to include
  979.       knowning that we will fail and/or it taking an unreasonable
  980.       amount of time (AKA Gecko).
  981.     """
  982.  
  983.     global endIsShowing
  984.     global beginningIsShowing
  985.     global cursorCell
  986.     global monitor
  987.  
  988.     if len(_lines) == 0:
  989.         if useBrlAPIBindings:
  990.             if brlAPIRunning:
  991.                 brlAPI.writeText("", 0)
  992.         else:
  993.             brl.writeText(0, "")
  994.         return
  995.  
  996.     # Now determine the location of the cursor.  First, we'll figure
  997.     # out the 1-based offset for where we want the cursor to be.  If
  998.     # the target cell is less than zero, it means an offset from the
  999.     # right hand side of the display.
  1000.     #
  1001.     if targetCursorCell < 0:
  1002.         targetCursorCell = _displaySize[0] + targetCursorCell + 1
  1003.  
  1004.     # Now, we figure out the 0-based offset for where the cursor
  1005.     # actually is in the string.
  1006.     #
  1007.     line = _lines[viewport[1]]
  1008.     [string, focusOffset, attributeMask] = line.getLineInfo(getLinkMask)
  1009.     cursorOffset = -1
  1010.     if focusOffset >= 0:
  1011.         cursorOffset = focusOffset + _regionWithFocus.cursorOffset
  1012.  
  1013.     # Now, if desired, we'll automatically pan the viewport to show
  1014.     # the cursor.  If there's no targetCursorCell, then we favor the
  1015.     # left of the display if we need to pan left, or we favor the
  1016.     # right of the display if we need to pan right.
  1017.     #
  1018.     if panToCursor and (cursorOffset >= 0):
  1019.         if len(string) <= _displaySize[0]:
  1020.             viewport[0] = 0
  1021.         elif targetCursorCell:
  1022.             viewport[0] = max(0, cursorOffset - targetCursorCell + 1)
  1023.         elif cursorOffset < viewport[0]:
  1024.             viewport[0] = max(0, cursorOffset)
  1025.         elif cursorOffset >= (viewport[0] + _displaySize[0]):
  1026.             viewport[0] = max(0, cursorOffset - _displaySize[0] + 1)
  1027.  
  1028.     startPos = viewport[0]
  1029.     endPos = startPos + _displaySize[0]
  1030.  
  1031.     # Now normalize the cursor position to BrlTTY, which uses 1 as
  1032.     # the first cursor position as opposed to 0.
  1033.     #
  1034.     cursorCell = cursorOffset - startPos
  1035.     if (cursorCell < 0) or (cursorCell >= _displaySize[0]):
  1036.         cursorCell = 0
  1037.     else:
  1038.         cursorCell += 1 # Normalize to 1-based offset
  1039.  
  1040.     logLine = "BRAILLE LINE:  '%s'" % string
  1041.     debug.println(debug.LEVEL_INFO, logLine)
  1042.     log.info(logLine.encode("UTF-8"))
  1043.     logLine = "     VISIBLE:  '%s', cursor=%d" % \
  1044.                     (string[startPos:endPos], cursorCell)
  1045.     debug.println(debug.LEVEL_INFO, logLine)
  1046.     log.info(logLine.encode("UTF-8"))
  1047.  
  1048.     substring = string[startPos:endPos]
  1049.     if useBrlAPIBindings:
  1050.         if brlAPIRunning:
  1051.             writeStruct = brlapi.WriteStruct()
  1052.             writeStruct.regionBegin = 1
  1053.             writeStruct.regionSize = len(substring)
  1054.             while writeStruct.regionSize < _displaySize[0]:
  1055.                 substring += " "
  1056.                 if attributeMask:
  1057.                     attributeMask += '\x00'
  1058.                 writeStruct.regionSize += 1
  1059.             writeStruct.text = substring
  1060.             writeStruct.cursor = cursorCell
  1061.  
  1062.             # [[[WDW - if you want to muck around with the dots on the
  1063.             # display to do things such as add underlines, you can use
  1064.             # the attrOr field of the write structure to do so.  The
  1065.             # attrOr field is a string whose length must be the same
  1066.             # length as the display and whose dots will end up showing
  1067.             # up on the display.  Each character represents a bitfield
  1068.             # where each bit corresponds to a dot (i.e., bit 0 = dot 1,
  1069.             # bit 1 = dot 2, and so on).  Here's an example that underlines
  1070.             # all the text.]]]
  1071.             #
  1072.             #myUnderline = ""
  1073.             #for i in range(0, _displaySize[0]):
  1074.             #    myUnderline += '\xc0'
  1075.             #writeStruct.attrOr = myUnderline
  1076.  
  1077.             if attributeMask:
  1078.                 writeStruct.attrOr = attributeMask[startPos:endPos]
  1079.  
  1080.             brlAPI.write(writeStruct)
  1081.     else:
  1082.         brl.writeText(cursorCell, substring)
  1083.  
  1084.     if settings.enableBrailleMonitor:
  1085.         if not monitor:
  1086.             monitor = brlmon.BrlMon(_displaySize[0])
  1087.             monitor.show_all()
  1088.         if attributeMask:
  1089.             subMask = attributeMask[startPos:endPos]
  1090.         else:
  1091.             subMask = None
  1092.         monitor.writeText(cursorCell, substring, subMask)
  1093.     elif monitor:
  1094.         monitor.destroy()
  1095.         monitor = None
  1096.  
  1097.     beginningIsShowing = startPos == 0
  1098.     endIsShowing = endPos >= len(string)
  1099.  
  1100. def displayRegions(regionInfo):
  1101.     """Displays a list of regions on a single line, setting focus to the
  1102.        specified region.  The regionInfo parameter is something that is
  1103.        typically returned by a call to braillegenerator.getBrailleRegions.
  1104.  
  1105.     Arguments:
  1106.     - regionInfo: a list where the first element is a list of regions
  1107.                   to display and the second element is the region
  1108.                   with focus (must be in the list from element 0)
  1109.     """
  1110.  
  1111.     regions = regionInfo[0]
  1112.     focusedRegion = regionInfo[1]
  1113.  
  1114.     clear()
  1115.     line = Line()
  1116.     for item in regions:
  1117.         line.addRegion(item)
  1118.     addLine(line)
  1119.     setFocus(focusedRegion)
  1120.     refresh()
  1121.  
  1122. def displayMessage(message, cursor=-1):
  1123.     """Displays a single line, setting the cursor to the given position,
  1124.     ensuring that the cursor is in view.
  1125.  
  1126.     Arguments:
  1127.     - message: the string to display
  1128.     - cursor: the 0-based cursor position, where -1 (default) means no cursor
  1129.     """
  1130.  
  1131.     clear()
  1132.     region = Region(message, cursor)
  1133.     addLine(Line(region))
  1134.     setFocus(region)
  1135.     refresh(True)
  1136.  
  1137. def panLeft(panAmount=0):
  1138.     """Pans the display to the left, limiting the pan to the beginning
  1139.     of the line being displayed.
  1140.  
  1141.     Arguments:
  1142.     - panAmount: the amount to pan.  A value of 0 means the entire
  1143.                  width of the physical display.
  1144.  
  1145.     Returns True if a pan actually happened.
  1146.     """
  1147.  
  1148.     oldX = viewport[0]
  1149.  
  1150.     if panAmount == 0:
  1151.         panAmount = _displaySize[0]
  1152.  
  1153.     if viewport[0] > 0:
  1154.         viewport[0] = max(0, viewport[0] - panAmount)
  1155.  
  1156.     return oldX != viewport[0]
  1157.  
  1158. def panRight(panAmount=0):
  1159.     """Pans the display to the right, limiting the pan to the length
  1160.     of the line being displayed.
  1161.  
  1162.     Arguments:
  1163.     - panAmount: the amount to pan.  A value of 0 means the entire
  1164.                  width of the physical display.
  1165.  
  1166.     Returns True if a pan actually happened.
  1167.     """
  1168.  
  1169.     oldX = viewport[0]
  1170.  
  1171.     if panAmount == 0:
  1172.         panAmount = _displaySize[0]
  1173.  
  1174.     if len(_lines) > 0:
  1175.         lineNum = viewport[1]
  1176.         newX = viewport[0] + panAmount
  1177.         [string, focusOffset, attributeMask] = _lines[lineNum].getLineInfo()
  1178.         if newX < len(string):
  1179.             viewport[0] = newX
  1180.  
  1181.     return oldX != viewport[0]
  1182.  
  1183. def panToOffset(offset):
  1184.     """Automatically pan left or right to make sure the current offset is
  1185.     showing."""
  1186.  
  1187.     while offset < viewport[0]:
  1188.         debug.println(debug.LEVEL_FINEST,
  1189.                       "braille.panToOffset (left) %d" % offset)
  1190.         if not panLeft():
  1191.             break
  1192.  
  1193.     while offset >= (viewport[0] + _displaySize[0]):
  1194.         debug.println(debug.LEVEL_FINEST,
  1195.                       "braille.panToOffset (right) %d" % offset)
  1196.         if not panRight():
  1197.             break
  1198.  
  1199. def returnToRegionWithFocus(inputEvent=None):
  1200.     """Pans the display so the region with focus is displayed.
  1201.  
  1202.     Arguments:
  1203.     - inputEvent: the InputEvent instance that caused this to be called.
  1204.  
  1205.     Returns True to mean the command should be consumed.
  1206.     """
  1207.  
  1208.     setFocus(_regionWithFocus)
  1209.     refresh(True)
  1210.  
  1211.     return True
  1212.  
  1213. def _processBrailleEvent(command):
  1214.     """Handles BrlTTY command events.  This passes commands on to Orca for
  1215.     processing.  If Orca does not handle them (as indicated by a return value
  1216.     of false from the callback passed to init, it will attempt to handle the
  1217.     command itself - either by panning the viewport or passing cursor routing
  1218.     keys to the Regions for handling.
  1219.  
  1220.     Arguments:
  1221.     - command: the BrlAPI command for the key that was pressed.
  1222.     """
  1223.  
  1224.     _printBrailleEvent(debug.LEVEL_FINE, command)
  1225.  
  1226.     # [[[TODO: WDW - DaveM suspects the Alva driver is sending us a
  1227.     # repeat flag.  So...let's kill a couple birds here until BrlTTY
  1228.     # 3.8 fixes the problem: we'll disable autorepeat and we'll also
  1229.     # strip out the autorepeat flag if this is the first press of a
  1230.     # button.]]]
  1231.     #
  1232.     if command & BRL_FLG_REPEAT_INITIAL:
  1233.         command &= ~(BRL_FLG_REPEAT_INITIAL | BRL_FLG_REPEAT_DELAY)
  1234.     elif command & BRL_FLG_REPEAT_DELAY:
  1235.         return True
  1236.  
  1237.     consumed = False
  1238.  
  1239.     if settings.timeoutCallback and (settings.timeoutTime > 0):
  1240.         signal.signal(signal.SIGALRM, settings.timeoutCallback)
  1241.         signal.alarm(settings.timeoutTime)
  1242.  
  1243.     if _callback:
  1244.         try:
  1245.             # Like key event handlers, a return value of True means
  1246.             # the command was consumed.
  1247.             #
  1248.             consumed = _callback(command)
  1249.         except:
  1250.             debug.printException(debug.LEVEL_WARNING)
  1251.             consumed = False
  1252.  
  1253.     if (command >= 0x100) and (command < (0x100 + _displaySize[0])):
  1254.         if len(_lines) > 0:
  1255.             cursor = (command - 0x100) + viewport[0]
  1256.             lineNum = viewport[1]
  1257.             _lines[lineNum].processRoutingKey(cursor)
  1258.             consumed = True
  1259.  
  1260.     if command in (0x2141, 65): # Toggle six dot braille
  1261.         settings.enableContractedBraille = not settings.enableContractedBraille
  1262.         for line in _lines:
  1263.             line.setContractedBraille(settings.enableContractedBraille)
  1264.         refresh()
  1265.  
  1266.     if settings.timeoutCallback and (settings.timeoutTime > 0):
  1267.         signal.alarm(0)
  1268.  
  1269.     return consumed
  1270.  
  1271. def _brlAPIKeyReader(source, condition):
  1272.     """Method to read a key from the BrlAPI bindings.  This is a
  1273.     gobject IO watch handler.
  1274.     """
  1275.     key = brlAPI.readKey(False)
  1276.     if key:
  1277.         #flags = key >> 32
  1278.         lower = key & 0xFFFFFFFF
  1279.         #keyType = lower >> 29
  1280.         keyCode = lower & 0x1FFFFFFF
  1281.         
  1282.         # [[TODO: WDW - HACK If we have a cursor routing key, map
  1283.         # it back to the code we used to get with earlier versions
  1284.         # of BrlAPI (i.e., bit 0x100 was the indicator of a cursor
  1285.         # routing key instead of 0x1000).  This may change before
  1286.         # the offical BrlAPI Python bindings are released.]]]
  1287.         #
  1288.         if keyCode & 0x10000:
  1289.             keyCode = 0x100 | (keyCode & 0xFF)
  1290.         if keyCode:
  1291.             _processBrailleEvent(keyCode)
  1292.     return brlAPIRunning
  1293.  
  1294. def setupKeyRanges(keys):
  1295.     """Hacky method to tell BrlTTY what to send and not send us via
  1296.     the readKey method.  This only works with BrlTTY v3.8 and better.
  1297.  
  1298.     Arguments:
  1299.     -keys: a list of BrlAPI commands.
  1300.     """
  1301.     if not brlAPIRunning:
  1302.         return
  1303.  
  1304.     try:
  1305.         # First, start by ignoring everything.
  1306.         #
  1307.         brlAPI.ignoreKeys(brlapi.rangeType_all, [0])
  1308.  
  1309.         # Next, enable cursor routing keys.
  1310.         #
  1311.         keySet = [brlapi.KEY_TYPE_CMD | brlapi.KEY_CMD_ROUTE]
  1312.  
  1313.         # Finally, enable the commands we care about.
  1314.         #
  1315.         for key in keys:
  1316.             keySet.append(brlapi.KEY_TYPE_CMD | key)
  1317.             
  1318.         brlAPI.acceptKeys(brlapi.rangeType_command, keySet)
  1319.  
  1320.         brlAPI.acceptKeys(brlapi.rangeType_key, [65])
  1321.  
  1322.         debug.println(debug.LEVEL_FINEST, "Using BrlAPI v0.5.0+")
  1323.     except:
  1324.         debug.printException(debug.LEVEL_FINEST)
  1325.         try:
  1326.             # Old, incompatible way that was in v3.8 devel, but
  1327.             # changed prior to release.  We need this just in case
  1328.             # people have not updated yet.
  1329.  
  1330.             # First, start by ignoring everything.
  1331.             #
  1332.             brlAPI.ignoreKeyRange(0,
  1333.                                   brlapi.KEY_FLAGS_MASK \
  1334.                                   | brlapi.KEY_TYPE_MASK \
  1335.                                   | brlapi.KEY_CODE_MASK)
  1336.  
  1337.             # Next, enable cursor routing keys.
  1338.             #
  1339.             brlAPI.acceptKeyRange(brlapi.KEY_TYPE_CMD | brlapi.KEY_CMD_ROUTE,
  1340.                                   brlapi.KEY_TYPE_CMD \
  1341.                                   | brlapi.KEY_CMD_ROUTE \
  1342.                                   | brlapi.KEY_CMD_ARG_MASK)
  1343.  
  1344.             # Finally, enable the commands we care about.
  1345.             #
  1346.             keySet = []
  1347.             for key in keys:
  1348.                 keySet.append(brlapi.KEY_TYPE_CMD | key)
  1349.             if len(keySet):
  1350.                 brlAPI.acceptKeySet(keySet)
  1351.  
  1352.             debug.println(debug.LEVEL_FINEST,
  1353.                           "Using BrlAPI pre-release v0.5.0")
  1354.         except:
  1355.             debug.printException(debug.LEVEL_FINEST)
  1356.             debug.println(
  1357.                 debug.LEVEL_WARNING,
  1358.                 "Braille module cannot listen for braille input events")
  1359.  
  1360. def init(callback=None, tty=7):
  1361.     """Initializes the braille module, connecting to the BrlTTY driver.
  1362.  
  1363.     Arguments:
  1364.     - callback: the method to call with a BrlTTY input event.
  1365.     - tty: the tty port to take ownership of (default = 7)
  1366.     Returns True if the initialization procedure was run or False if this
  1367.     module has already been initialized.
  1368.     """
  1369.  
  1370.     global _initialized
  1371.     global _displaySize
  1372.     global _callback
  1373.  
  1374.     if _initialized:
  1375.         return False
  1376.  
  1377.     _callback = callback
  1378.  
  1379.     if useBrlAPIBindings:
  1380.         try:
  1381.             global brlAPI
  1382.             global brlAPIRunning
  1383.             global brlAPISourceId
  1384.  
  1385.             gobject.threads_init()
  1386.             brlAPI = brlapi.Connection()
  1387.  
  1388.             try:
  1389.                 import os
  1390.                 windowPath = os.environ["WINDOWPATH"]
  1391.                 brlAPI.enterTtyModeWithPath()
  1392.                 brlAPIRunning = True
  1393.                 debug.println(\
  1394.                     debug.LEVEL_CONFIGURATION,
  1395.                     "Braille module has been initialized using WINDOWPATH=" \
  1396.                     + "%s" % windowPath)
  1397.             except:
  1398.                 brlAPI.enterTtyMode(tty)
  1399.                 brlAPIRunning = True
  1400.                 debug.println(\
  1401.                     debug.LEVEL_CONFIGURATION,
  1402.                     "Braille module has been initialized using tty=%d" % tty)
  1403.             brlAPISourceId = gobject.io_add_watch(brlAPI.fileDescriptor,
  1404.                                                   gobject.IO_IN,
  1405.                                                   _brlAPIKeyReader)
  1406.         except:
  1407.             debug.printException(debug.LEVEL_FINEST)
  1408.             return False
  1409.     else:
  1410.         if brl.init(tty):
  1411.             debug.println(debug.LEVEL_CONFIGURATION,
  1412.                           "Braille module has been initialized.")
  1413.             brl.registerCallback(_processBrailleEvent)
  1414.         else:
  1415.             debug.println(debug.LEVEL_CONFIGURATION,
  1416.                           "Braille module has NOT been initialized.")
  1417.             return False
  1418.  
  1419.     # [[[TODO: WDW - For some reason, BrlTTY wants to say the height of the
  1420.     # Vario is 40 so we hardcode it to 1 for now.]]]
  1421.     #
  1422.     #_displaySize = (brl.getDisplayWidth(), brl.getDisplayHeight())
  1423.     if useBrlAPIBindings:
  1424.         (x, y) = brlAPI.displaySize
  1425.         _displaySize = [x, 1]
  1426.     else:
  1427.         _displaySize = [brl.getDisplayWidth(), 1]
  1428.  
  1429.     debug.println(debug.LEVEL_CONFIGURATION,
  1430.                   "braille display size = (%d, %d)" \
  1431.                   % (_displaySize[0], _displaySize[1]))
  1432.  
  1433.     clear()
  1434.     refresh(True)
  1435.  
  1436.     _initialized = True
  1437.  
  1438.     return True
  1439.  
  1440. def shutdown():
  1441.     """Shuts down the braille module.   Returns True if the shutdown procedure
  1442.     was run or False if this module has not been initialized.
  1443.     """
  1444.  
  1445.     global _initialized
  1446.  
  1447.     if not _initialized:
  1448.         return False
  1449.  
  1450.     global brlAPIRunning
  1451.     global brlAPISourceId
  1452.  
  1453.     if useBrlAPIBindings:
  1454.         if brlAPIRunning:
  1455.             brlAPIRunning = False
  1456.             gobject.source_remove(brlAPISourceId)
  1457.             brlAPISourceId = 0
  1458.             brlAPI.leaveTtyMode()
  1459.     else:
  1460.         brl.shutdown()
  1461.  
  1462.     _initialized = False
  1463.  
  1464.     return True
  1465.